/*

 util工具类

 根据实际情况自定义的使用工具封装类

*/

const cron = require('cron').CronJob
const request = require('koa2-request')
const crypto = require('crypto')
const uuid = require('uuid')

class common {

	constructor(_extends) {
		Object.assign(this, _extends)
	}

	resolve(data = 0) {
		return new Promise((resolve) => {
			resolve(data)
		})
	}

	reject(code, msg) {
		return new Promise((resolve, reject) => {
			reject({
				code,
				msg
			})
		})
	}

	//判断是否数字
	isNum(obj) {
		return !isNaN(obj)
	}

	//判断是否字符串
	isString(obj) {
		return Object.prototype.toString.call(obj) === '[object String]'
	}

	//判断是否数组
	isArray(obj) {
		return Object.prototype.toString.call(obj) === '[object Array]'
	}

	isExists(obj) {
		return typeof(obj) != 'undefined'
	}

	//打印边框消息
	borderMsg(...msg) {
		this.borderLog('log', ...msg)
	}

	//打印边框错误消息
	borderErr(...msg) {
		this.borderLog('error', ...msg)
	}

	borderLog(type, ...msg) {
		let str = '╔'
		let max = 0
		for(let m of msg) 
			max = m.length > max ? m.length : max
		for(let i = 0;i < max + 2;i++) {
			str += '═'
		}
		str += '╗\n'
		for(let j = 0;j < msg.length;j++) {
			str += '║ '
			let s = msg[j]
			while(s.length < max) s += ' '
			str += s
			str += ' ║\n'
		}
		str += '╚'
		for(let i = 0;i < max + 2;i++) {
			str += '═'
		}
		str += '╝'
		console[type](str)
	}

	//异步写入log文件
	writeLog(type, msg = '', notPrint = true, error) {
		try {
			if(error) {
				if(error.code)
					msg += ` [${error.code} ${error.msg}]`
				else
					msg += error.toString()
			}
			let time = this.timestamp2str(this.timestamp(), true, true, true, true, true, true, '/')
			let log = `${time} [${type}]: ${msg}\r\n`
			this.fs.outputFile(this.config.frame.logFile, log, { flag: 'a' }, (err) => {
				if(err) {
					console.error('GTCCS服务写入日志失败', err)
				this.alertMsg('写入日志失败', `GTCCS服务的日志机制出错无法写入日志内容：[${type}]${msg}`, err)
				}
			})
			if(!notPrint) {
				if(type == 'error') {
					console.trace()
					console.error(`${log} error is wrote to ${process.cwd()}/error.log`)
				}
				else
					console.log(`${log} log is wrote`)
			}
		}
		catch(err) {
			console.error('log failed:', err)
		}
	}

	writeLogSync(type, msg, notPrint = true) {
		return new Promise( async (resolve, reject) => {
			let time = this.timestamp2str(this.timestamp(), true, true, true, true, true, true, '/')
			let log = `${time} [${type}]: ${msg}\r\n`
			fs.outputFile(this.config.frame.logFile, log, {
				flag: 'a'
			}, (err) => {
				if(notPrint)
					return
				if(err) {
					console.error('log failed:', err)
					reject({
						code: '-1064',
						msg: 'write log failed'
					})
				}
				if(type == 'error') {
					console.trace()
					console.error(`${log} error is wrote to ${process.cwd()}/error.log`)
				}
				else
					console.log(`${log} log is wrote`)
				resolve()
			})
		})
	}

	alertMsg(title = '', content = '', error) {
		const alertUrl = this.config.frame.alertUrl
		if(error) {
			if(error.code)
				content += ` [${error.code} ${error.msg}]`
			else
				content += error.toString()
		}
		console.trace()
		console.error(title, content, error)
		this.reqPost({
			url: alertUrl,
			headers: {
				'content-type': 'application/x-www-form-urlencoded'
			},
			data: {
				text: title,
				desp: content
			}
		}).catch((err) => {
			this.writeLog('error', 'send alert to master failed ' + err.toString(), false).catch((_err) => {
				console.error('send alert to master  and write log failed', _err)
			})
		})
	}

	//发起GET请求
	async reqGet(params) {
		try {
			let {url, data, headers, cookie, json, getCookie, getStatus, getContentInfo, getBuffer } = params
			json = typeof(json) == 'undefined' ? true : json
			let reqUri = ''
			if(data) {
				reqUri = '?'
				for(let key in data) {
					reqUri += `${key}=${data[key]}&`
				}
				url += reqUri.substr(0, reqUri.length - 1)
			}
			let jar = cookie ? request.jar() : false
			if(cookie)
				for(let url in cookie)
					jar.setCookie(request.cookie(await this.cookieStringify(cookie[url])), url)
			let result = await request({
				url,
				method: 'get',
				headers: headers || {'content-type': 'application/json', charset: 'UTF-8'},
				json: json,
				encoding: getBuffer ? null : undefined,
				jar
			})
			let extData = {}
			if(getCookie) {
				Object.assign(extData, {
					cookie: await this.cookieParseByArr(result.headers['set-cookie'])
				})
			}
			if(getContentInfo) {
				Object.assign(extData, {
					contentType: result.headers['content-type'],
					contentLen: result.headers['content-length']
				})
			}
			if(getStatus) {
				Object.assign(extData, {
					status: {
						code: result.statusCode,
						msg: result.statusMessage
					}
				})
			}
			return this.resolve(!getCookie && !getContentInfo && !getStatus ? result.body : Object.assign(extData, {data: result.body}))
		}
		catch(err) {
			return this.reject( '-1019', 'send GET request failed')
		}
	}

	//发起POST请求
	async reqPost(params) {
		try {
			let {url, data, headers, cookie, getData, json, getCookie, getContentInfo, getStatus, getBuffer} = params
			let reqUri = ''
			data = data || {}
			json = typeof(json) == 'undefined' ? true : json
			if(getData) {
				reqUri = '?'
				for(let key in getData) {
					reqUri += `${key}=${getData[key]},`
				}
				url += reqUri.substr(0, reqUri.length - 1)
			}
			let jar = cookie ? request.jar() : false
			if(cookie)
				for(let url in cookie)
					jar.setCookie(request.cookie(await this.cookieStringify(cookie[url])), url)
			let requestData = {
				url,
				method: 'post',
				headers: headers || {'content-type': 'application/json', charset: 'UTF-8'},
				json: json,
				encoding: getBuffer ? null : undefined,
				jar
			}
			requestData.headers.charset = 'UTF-8'
			switch(requestData.headers['content-type']) {
				case 'application/x-www-form-urlencoded':
					requestData.form = data
				break
				case 'application/json':
				case 'application/xml':
					requestData.body = data
				break
			}
			let result = await request(requestData)
			let extData = {}
			if(getCookie) {
				Object.assign(extData, {
					cookie: await this.cookieParseByArr(result.headers['set-cookie'])
				})
			}
			if(getContentInfo) {
				Object.assign(extData, {
					contentType: result.headers['content-type'],
					contentLen: result.headers['content-length']
				})
			}
			if(getStatus) {
				Object.assign(extData, {
					status: {
						code: result.statusCode,
						msg: result.statusMessage
					}
				})
			}
			return this.resolve(!getCookie && !getContentInfo && !getStatus ? result.body : Object.assign(extData, {data: result.body}))
		}
		catch(err) {
			console.error(err)
			return this.reject('-1020', 'send POST request failed')
		}
	}

	//cookie字符串转换为对象
	cookieParseByStr(cookieStr) {
		try {
			let obj = {}
			let temp = cookieStr.split(';')
			for(let data of temp) {
				let kv = data.split('=')
				if(!kv || !kv[0] || !kv[1])
					throw {code:'-1040', msg:'parse cookie str to object failed'}
				obj[this.trim(kv[0])] = kv[1]
			}
			return obj
		}
		catch(err) {
			console.error(err)
			throw {code:'-1040', msg:'parse cookie str to object failed'}
		}
	}

	//转换cookie字符串数组为对象数组
	cookieParseByArr(cookieArr = []) {
		try {
			let arr = []
			for(let c of cookieArr) {
				let temp = this.cookieParseByStr(c)
				arr.push(temp)
			}
			return arr
		}
		catch(err) {
			console.error(err)
			throw {code:'-1042', msg:'parse cookie array to object array failed'}
		}
	}

	//对象转换为cookie字符串
	cookieStringify(cookieObj) {
		try {
			let str = ''
			for(let k in cookieObj) {
				str += `; ${k}=${cookieObj[k]}`
			}
			str = ltrim(str, '; ')
			return str
		}
		catch(err) {
			console.error(err)
			throw {code:'-1041', msg:'stringify object to cookie str failed'}
		}
	}

	trim(str, symbol = '\\s') {
		return str.replace(new RegExp(`(^${symbol}*)|(${symbol}*$)`, 'g'), '')
	}

	ltrim(str, symbol = '\\s') {
		return str.replace(new RegExp(`(^${symbol}*)`, 'g'), '')
	}

	rtrim(str, symbol = '\\s') {
		return str.replace(new RegExp(`(${symbol}*$)`, 'g'), '')
	}

	//获得当前时间戳
	timestamp() {
		return parseInt(Date.now() / 1000)
	}

	//将时间戳转字符串日期
	 timestamp2str(timestamp, year = true, month = true, day = true, hours, min, second, split = '-') {
		try {
			let date = new Date(timestamp.toString().length == 10 ? timestamp * 1000 : timestamp)
			let _year = `${year ? (date.getFullYear() > 9 ? date.getFullYear() : '0' + date.getFullYear()) + split : ''}`
			let _month = `${month ? (date.getMonth() + 1 > 9 ? date.getMonth() + 1 : '0' + (date.getMonth() + 1)) + split : ''}`
			let _day = `${day ? (date.getDate() > 9 ? date.getDate() : '0' + date.getDate()) : ''}`
			let _hours = `${hours ? ' ' + (date.getHours() > 9 ? date.getHours() : '0' + date.getHours()) + ':' : ''}`
			let _min = `${min ? (date.getMinutes() > 9 ? date.getMinutes() : '0' + date.getMinutes()) + ':' : ''}`
			let _second = `${second ? (date.getSeconds() > 9 ? date.getSeconds() : '0' + date.getSeconds()) : ''}`
			return `${_year}${_month}${_day}${_hours}${_min}${_second}`
		}
		catch(err) {
			console.error(err)
			throw {code:'-1017', msg:'timestamp convert to date string failed'}
		}
	}

	//生成版本一的UUID
	uuid1() {
		return uuid.v1()
	}

	//生成版本四的UUID
	uuid4() {
		return uuid.v4()
	}

	//对字符串进行md5计算
	md5(str) {
		try {
			let cryptStr = crypto.createHash('md5').update(str).digest('hex')
			return cryptStr
		}
		catch(err) {
			console.error(err)
			throw {code:'-1058', msg:'crypto data failed'}
		}
	}

	//对字符串进行sha1计算
	sha1(str) {
		try {
			let cryptStr = crypto.createHash('sha1').update(str).digest('hex')
			return cryptStr
		}
		catch(err) {
			console.error(err)
			throw {code:'-1055', msg:'crypto data failed'}
		}
	}

	//对字符串进行sha256计算
	sha256(str) {
		try {
			let cryptStr = crypto.createHash('sha256').update(str).digest('hex')
			return cryptStr
		}
		catch(err) {
			console.error(err)
			throw {code:'-1056', msg:'crypto data failed'}
		}
	}

	//对字符串进行sha512计算
	sha512(str) {
		try {
			let cryptStr = crypto.createHash('sha512').update(str).digest('hex')
			return cryptStr
		}
		catch(err) {
			console.error(err)
			throw {code:'-1057', msg:'crypto data failed'}
		}
	}

	//检查定时任务是否正在运行
	checkJobRun(jobId) {
		return Boolean(jobId && this.cronPool[jobId] && this.cronPool[jobId].running)
	}

	//创建定时任务
	createJob(jobId, jobPlan, jobTarget, jobStopTarget = null, jobErrTarget = null, jobStartNow = false, jobLock = false) {
		if(this.checkJobRun(jobId))
			this.stopJob(jobId)
		try {
			jobErrTarget = jobErrTarget || ((_err) => {this.writeLog('error', 'target job fun error:', false, _err)})
			this.cronPool[jobId] = new cron(jobPlan, () => {
				try {
					if(this.cronPool[jobId].locked && jobLock)
						return
					this.cronPool[jobId].locked = true
					jobTarget.function(jobTarget.params).then(() => {
						this.cronPool[jobId].locked = false
					}).catch(jobErrTarget)
				}
				catch(err) {
					console.error(err)
					this.writeLog('error', 'target job fun error', false, err)
				}
			},  () => {
				try {
					if(!jobStopTarget)
						return
					if(Object.prototype.toString.call(jobStopTarget.function)== '[object AsyncFunction]')
						(jobStopTarget.function(jobStopTarget.params)).catch(jobErrTarget)
					else
						jobStopTarget.function(jobStopTarget.params)
				}
				catch(err) {
					console.error(err)
					this.writeLog('error', 'target job fun error', false, err)
				}
			}, jobStartNow, 'Asia/Chongqing')
		}
		catch(err) {
			console.error(err)
			throw {code:'-1051', msg:'timed task str invalid'}
		}
		console.log(`timed task ${jobId} created${jobStartNow ? ' and task started' : ''}`)
	}

	//开始定时任务
	startJob(jobId) {
		if(!this.checkJobRun(jobId))
			this.cronPool[jobId].start()
		console.log(`timed task ${jobId} started`)
	}

	//停止定时任务
	stopJob(jobId) {
		if(this.checkJobRun(jobId))
			this.cronPool[jobId].stop()
		console.error(`timed task ${jobId} stopped`)
	}

	clearJob() {
		for(let jobId in this.cronPool) {
			this.stopJob(jobId)
		}
	}

}

module.exports = common